﻿using System;
using System.Data;
using System.Reflection;
using System.Text;
using AMS.DataAccess.Common;
using AMS.DataAccess.Properties;

namespace AMS.DataAccess.Sql
{
    /// <summary>
    /// 构造INSERT语句的类。
    /// 注：它并不是万能的，有些INSERT语句并不能构造出来。比如 SELECT INTO
    /// </summary>
    public class SqlInsert : SqlStatement
    {
        /// <summary>
        /// 构造INSERT语句的类。
        /// </summary>
        /// <param name="tableName">表名</param>
        public SqlInsert(string tableName)
            : this(tableName, false)
        {
        }

        /// <summary>
        /// 构造INSERT语句的类。
        /// </summary>
        /// <param name="tableName">表名</param>
        /// <param name="compactMode">是否紧凑模式</param>
        public SqlInsert(string tableName, bool compactMode)
        {
            if (string.IsNullOrEmpty(tableName))
            {
                throw new ArgumentNullException(Resources.TableNameRequired);
            }

            this.tableName = tableName;
            this.CompactMode = compactMode;
            sbSql = new StringBuilder();
            sbColumns = new StringBuilder();
            sbValues = new StringBuilder();
            sbAppendix = new StringBuilder();
        }

        #region Private FIelds
        /// <summary>
        /// 表达式
        /// </summary>
        private StringBuilder sbSql;

        /// <summary>
        /// 表名
        /// </summary>
        private string tableName;

        /// <summary>
        /// 获取表名
        /// </summary>
        public string TableName
        {
            get { return tableName; }
        }

        private StringBuilder sbColumns;
        private StringBuilder sbValues;
        private StringBuilder sbAppendix;

        /// <summary>
        /// 获取或设置一个值，表示是否当某列的值为NULL时也输出到SQL语句中
        /// </summary>
        public bool ReserveNullColumns { get; set; }
        #endregion

        #region Private Methods
        /// <summary>
        /// 添加一个属性列
        /// </summary>
        /// <param name="column"></param>
        /// <param name="value"></param>
        /// <param name="property"></param>
        /// <param name="attribute"></param>
        private void AddObjectColumn(string column, object value, PropertyInfo property, DBColumnAttribute attribute)
        {
            if (value == null && ReserveNullColumns)
            {
                AddColumnNullValue(column);
                return;
            }

            Type propertyType = property.PropertyType;
            if (propertyType.Equals(typeof(string)))
            {
                string svalue = AdoConverter.ToString(value);
                // NOTE: treat empty as null
                if (ReserveNullColumns ||
                    !string.IsNullOrEmpty(svalue) ||
                    (svalue == string.Empty && KeepEmptyString))
                {
                    AddColumn(column, svalue);
                }
            }
            else if (propertyType.Equals(typeof(int)) || propertyType.IsSubclassOf(typeof(Enum)))
            {
                if (attribute.NullValue != null)
                {
                    int ivalue = AdoConverter.ToInt32(value);
                    if (ivalue != AdoConverter.ToInt32(attribute.NullValue))
                    {
                        AddColumn(column, ivalue);
                    }
                    else if (ReserveNullColumns)
                    {
                        AddColumnNullValue(column);
                    }
                }
                else
                {
                    AddColumn(column, AdoConverter.ToInt32(value));
                }
            }
            else if (propertyType.Equals(typeof(int?)))
            {
                if (value != null)
                {
                    AddColumn(column, AdoConverter.ToInt32(value));
                }
                else if (ReserveNullColumns)
                {
                    AddColumnNullValue(column);
                }
            }
            else if (propertyType.Equals(typeof(long)))
            {
                if (attribute.NullValue != null)
                {
                    long ivalue = AdoConverter.ToInt64(value);
                    if (ivalue != AdoConverter.ToInt64(attribute.NullValue))
                    {
                        AddColumn(column, ivalue);
                    }
                    else if (ReserveNullColumns)
                    {
                        AddColumnNullValue(column);
                    }
                }
                else
                {
                    AddColumn(column, AdoConverter.ToInt64(value));
                }
            }
            else if (propertyType.Equals(typeof(long?)))
            {
                if (value != null)
                {
                    AddColumn(column, AdoConverter.ToInt64(value));
                }
                else if (ReserveNullColumns)
                {
                    AddColumnNullValue(column);
                }
            }
            else if (propertyType.Equals(typeof(Guid)))
            {
                Guid ivalue = AdoConverter.ToGuid(value);
                if (attribute.NullValue != null)
                {
                    if (!ivalue.ToString().Equals(attribute.NullValue.ToString(), StringComparison.InvariantCultureIgnoreCase))
                    {
                        AddColumn(column, ivalue);
                    }
                    else if (ReserveNullColumns)
                    {
                        AddColumnNullValue(column);
                    }
                }
                else
                {
                    if (ivalue != Guid.Empty)
                    {
                        AddColumn(column, AdoConverter.ToGuid(value));
                    }
                    else if (ReserveNullColumns)
                    {
                        AddColumnNullValue(column);
                    }
                }
            }
            else if (propertyType.Equals(typeof(short)))
            {
                if (attribute.NullValue != null)
                {
                    short ivalue = AdoConverter.ToShort(value);
                    if (ivalue != AdoConverter.ToShort(attribute.NullValue))
                    {
                        AddColumn(column, ivalue);
                    }
                    else if (ReserveNullColumns)
                    {
                        AddColumnNullValue(column);
                    }
                }
                else
                {
                    AddColumn(column, AdoConverter.ToShort(value));
                }
            }
            else if (propertyType.Equals(typeof(short?)))
            {
                if (value != null)
                {
                    AddColumn(column, AdoConverter.ToShort(value));
                }
                else if (ReserveNullColumns)
                {
                    AddColumnNullValue(column);
                }
            }
            else if (propertyType.Equals(typeof(decimal)))
            {
                AddColumn(column, AdoConverter.ToDecimal(value));
            }
            else if (propertyType.Equals(typeof(decimal?)))
            {
                if (value != null)
                {
                    AddColumn(column, AdoConverter.ToDecimal(value));
                }
                else if (ReserveNullColumns)
                {
                    AddColumnNullValue(column);
                }
            }
            else if (propertyType.Equals(typeof(DateTime)))
            {
                if (attribute is DBDateTimeAttribute)
                {
                    DateTimeType dtType = ((DBDateTimeAttribute)attribute).DateTimeType;
                    DateTime dt = AdoConverter.ToDateTime(value);
                    if (dt != DateTime.MinValue)
                    {
                        AddColumn(column, dt, dtType);
                    }
                    else if (ReserveNullColumns)
                    {
                        AddColumnNullValue(column);
                    }
                }
                else
                {
                    throw new AttributeException(string.Format(Resources.DBDateTimeAttributeRequiredFormat, property.Name));
                }
            }
            else if (propertyType.Equals(typeof(DateTime?)))
            {
                if (value != null)
                {
                    DateTimeType dtType = ((DBDateTimeAttribute)attribute).DateTimeType;
                    AddColumn(column, AdoConverter.ToDateTime(value), dtType);
                }
                else if (ReserveNullColumns)
                {
                    AddColumnNullValue(column);
                }
            }
            else if (propertyType.Equals(typeof(bool)))
            {
                AddColumn(column, AdoConverter.ToBoolean(value));
            }
            else if (propertyType.Equals(typeof(bool?)))
            {
                if (value != null)
                {
                    AddColumn(column, AdoConverter.ToBoolean(value));
                }
                else if (ReserveNullColumns)
                {
                    AddColumnNullValue(column);
                }
            }
            else
            {
                throw new NotSupportedException(string.Format(Resources.DataTypeNotSupportedFormat, column));
            }
        }

        private void Prepare()
        {
            if (sbColumns.Length > 0)
            {
                sbColumns.Append(CompactMode ? ", " : "    ,");
                sbValues.Append(CompactMode ? ", " : "    ,");
            }
            else
            {
                sbColumns.Append(CompactMode ? "" : "    ");
                sbValues.Append(CompactMode ? "" : "    ");
            }
        }
        #endregion

        #region Public Methods
        /// <summary>
        /// 添加一个业务对象
        /// </summary>
        /// <param name="businessObject">业务对象</param>
        public void AddBusinessObject(object businessObject)
        {
            AddBusinessObject(businessObject, businessObject.GetType());
        }

        /// <summary>
        /// 添加一个业务对象
        /// </summary>
        /// <param name="businessObject">业务对象</param>
        /// <param name="objectType">对象的类型</param>
        public void AddBusinessObject(object businessObject, Type objectType)
        {
            if (objectType.IsInterface)
            {
                throw new ArgumentException(Resources.InterfaceTypeNotSupport);
            }

            if (businessObject.GetType() != objectType && !businessObject.GetType().IsSubclassOf(objectType))
            {
                throw new ArgumentException(Resources.TypeOfBOCanOnlyBeTheGivenType);
            }

            ObjectInfo objectInfo = ObjectUtils.GetBOObjectInfo(objectType);
            foreach (string key in objectInfo.DBFields.Keys)
            {
                PropertyInfo property = objectInfo.DBFields[key];
                object[] attributes = property.GetCustomAttributes(true);
                foreach (Attribute attr in attributes)
                {
                    if (attr is DBColumnAttribute)
                    {
                        if (!((DBColumnAttribute)attr).IgnoreInsert)
                        {
                            object retValue = ObjectUtils.GetPropertyValue(businessObject, property);
                            if (retValue != null || ReserveNullColumns)
                            {
                                AddObjectColumn(key, retValue, property, (DBColumnAttribute)attr);
                            }
                        }

                        break;
                    }
                }
            }
        }

        /// <summary>
        /// 添加一个 DataRow 作为数据
        /// </summary>
        /// <param name="row">要添加的 row</param>
        public void AddDataRow(DataRow row)
        {
            DataTable dt = row.Table;
            for (int i = 0; i < dt.Columns.Count; i++)
            {
                AddColumn(dt.Columns[i].ColumnName, row[i]);
            }
        }

        /// <summary>
        /// 添加一个表达式
        /// </summary>
        /// <param name="statement">表达式</param>
        public void Add(SqlStatement statement)
        {
            NL(sbAppendix.Append(statement.ToString()));
        }

        /// <summary>
        /// 添加一个表达式
        /// </summary>
        /// <param name="sql">表达式语句</param>
        public void Add(string sql)
        {
            NL(sbAppendix.Append(sql));
        }

        /// <summary>
        /// 添加一个列，设置其值为空
        /// </summary>
        /// <param name="columnName">表名</param>
        public void AddColumnNullValue(string columnName)
        {
            Prepare();
            NL(sbColumns.Append(columnName));
            NL(sbValues.Append("NULL"));
        }

        /// <summary>
        /// 添加一个列
        /// </summary>
        /// <param name="columnName">列名</param>
        /// <param name="value">列的值</param>
        public void AddColumn(string columnName, string value)
        {
            Prepare();
            NL(sbColumns.Append(columnName));
            NL(sbValues.Append(SqlStringUtils.QuotedString(value, true)));
        }

        /// <summary>
        /// 添加一个数值列
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="columnName"></param>
        /// <param name="value"></param>
        private void AddNumberColumn<T>(string columnName, T value)
        {
            Prepare();
            NL(sbColumns.Append(columnName));
            NL(sbValues.Append(value.ToString()));
        }

        /// <summary>
        /// 添加一个列
        /// </summary>
        /// <param name="columnName">列名</param>
        /// <param name="value">列的值</param>
        public void AddColumn(string columnName, int value)
        {
            AddNumberColumn<int>(columnName, value);
        }

        /// <summary>
        /// 添加一个列
        /// </summary>
        /// <param name="columnName">列名</param>
        /// <param name="value">列的值</param>
        public void AddColumn(string columnName, short value)
        {
            AddNumberColumn<short>(columnName, value);
        }

        /// <summary>
        /// 添加一个列
        /// </summary>
        /// <param name="columnName">列名</param>
        /// <param name="value">列的值</param>
        public void AddColumn(string columnName, long value)
        {
            AddNumberColumn<long>(columnName, value);
        }

        /// <summary>
        /// 添加一个列
        /// </summary>
        /// <param name="columnName">列名</param>
        /// <param name="value">列的值</param>
        public void AddColumn(string columnName, decimal value)
        {
            AddNumberColumn<decimal>(columnName, value);
        }

        /// <summary>
        /// 添加一个列
        /// </summary>
        /// <param name="columnName">列名</param>
        /// <param name="value">列值</param>
        public void AddColumn(string columnName, Guid value)
        {
            Prepare();
            NL(sbColumns.Append(columnName));
            NL(sbValues.Append(SqlStringUtils.QuotedString(value.ToString())));
        }

        /// <summary>
        /// 添加一个日期列
        /// </summary>
        /// <param name="columnName">列名</param>
        /// <param name="value">列值</param>
        /// <param name="kind">日期类型</param>
        public void AddColumn(string columnName, DateTime value, DateTimeType kind)
        {
            if (value == DateTime.MinValue || value == DateTime.MaxValue)
            {
                return;
            }

            Prepare();
            NL(sbColumns.Append(columnName));

            string strValue = SqlStringUtils.DateTimeToString(value, kind);
            NL(sbValues.Append(SqlStringUtils.QuotedString(strValue)));
        }

        /// <summary>
        /// 添加一个列
        /// </summary>
        /// <param name="columnName">列名</param>
        /// <param name="value">列值</param>
        public void AddColumn(string columnName, bool value)
        {
            Prepare();
            NL(sbColumns.Append(columnName));
            NL(sbValues.Append(value ? "1" : "0"));
        }

        /// <summary>
        /// 添加一个列
        /// </summary>
        /// <param name="columnName">列名</param>
        /// <param name="value">列值</param>
        public void AddColumn(string columnName, object value)
        {
            Prepare();
            NL(sbColumns.Append(columnName));
            NL(sbValues.Append(SqlStringUtils.ObjectToSqlString(value)));
        }

        /// <summary>
        /// 构造 SQL 表达式
        /// </summary>
        /// <returns></returns>
        public override string ToString()
        {
            if (sbColumns.Length == 0 && sbAppendix.Length == 0)
            {
                throw new SqlBuildingException(Resources.SqlStatementInvalid);
            }

            sbSql = new StringBuilder();
            sbSql.Append("INSERT INTO ");
            NL(sbSql.Append(tableName));

            if (sbColumns.Length > 0)
            {
                NL(sbSql.Append("("));
                sbSql.Append(sbColumns.ToString());
                NL(sbSql.Append(")"));
            }

            if (sbValues.Length > 0)
            {
                if (CompactMode)
                {
                    sbSql.Append(" VALUES(");
                }
                else
                {
                    sbSql.AppendLine("VALUES(");
                }

                sbSql.Append(sbValues.ToString());
                NL(sbSql.Append(")"));
            }

            if (sbAppendix.Length > 0)
            {
                sbSql.Append(sbAppendix.ToString());
            }

            return sbSql.ToString();
        }
        #endregion
    }
}
